#pragma once
#include "algebra.h"
#include <iostream>

namespace krryn{
namespace math{
	template<typename T, typename _Tag>
	class vector3{
	public:
		union{
            struct{ T m_X, m_Y, m_Z; };
            struct{ T m_R, m_G, m_B; };
        };

        vector3() 
            : m_X(0), m_Y(0), m_Z(0) {}
            
		vector3(T a_X, T a_Y, T a_Z) 
            : m_X(a_X), m_Y(a_Y), m_Z(a_Z) {}

		static vector3 identity(){
			return vector3(1, 1, 1);
		}

		static vector3 zero(){
			return vector3(0, 0, 0);
		}
		
        inline vector3 cross(const vector3 &a_Other) const{
            return vector3(
                m_Y * a_Other.m_Z - m_Z * a_Other.m_Y,
                m_Z * a_Other.m_X - m_X * a_Other.m_Z,
                m_X * a_Other.m_Y - m_Y * a_Other.m_Y
            );
        }
		
		inline T dot(const vector3 &a_Other) const{
			return m_X * a_Other.m_X
				 + m_Y * a_Other.m_Y
				 + m_Z * a_Other.m_Z;
		}
		
		inline T length() const{ 
			return math::sqrt(m_X * m_X + m_Y * m_Y + m_Z * m_Z); 
		}
        
        inline T sqr_length() const{
            return m_X * m_X + m_Y * m_Y + m_Z * m_Z;
        }
		
		inline vector3 normalize() const{
			const T l_TempLength = length();
            if(l_TempLength != 0){
                const T l_Length = 1.f / l_TempLength;
                return vector3(m_X * l_Length, m_Y * l_Length, m_Z * l_Length);
            }else{
				return vector3::zero();
            }
		}
        
        inline vector3 simple_linear_combination(const vector3 &a_V2, const T a_Amount) const{
            return *this * (1.f - a_Amount) + a_V2 * a_Amount;
        }
        
        inline vector3 linear_combination(const vector3 &a_V1, const T a_A1, 
                                          const vector3 &a_V2, const T a_A2, 
										  const vector3 &a_V3, const T a_A3) const{
            return a_V1 * a_A1 + a_V2 * a_A2 + a_V3 * a_A3;
        }

		inline vector3 operator*=(const vector3 &a_Other){
			m_X *= a_Other.m_X;
			m_Y *= a_Other.m_Y;
			m_Z *= a_Other.m_Z;
			return *this;
		}

		inline vector3 operator/=(const vector3 &a_Other){
			m_X /= a_Other.m_X;
			m_Y /= a_Other.m_Y;
			m_Z /= a_Other.m_Z;
			return *this;
		}

		inline vector3 operator+=(const vector3 &a_Other){
			m_X += a_Other.m_X;
			m_Y += a_Other.m_Y;
			m_Z += a_Other.m_Z;
			return *this;
		}

		inline vector3 operator-=(const vector3 &a_Other){
			m_X -= a_Other.m_X;
			m_Y -= a_Other.m_Y;
			m_Z -= a_Other.m_Z;
			return *this;
		}

		inline vector3 operator*=(const T a_Scalar){
			m_X *= a_Scalar;
			m_Y *= a_Scalar;
			m_Z *= a_Scalar;
			return *this;
		}

		inline vector3 operator/=(const T a_Scalar){
			m_X /= a_Scalar;
			m_Y /= a_Scalar;
			m_Z /= a_Scalar;
			return *this;
		}

		inline vector3 operator+=(const T a_Scalar){
			m_X += a_Scalar;
			m_Y += a_Scalar;
			m_Z += a_Scalar;
			return *this;
		}

		inline vector3 operator-=(const T a_Scalar){
			m_X -= a_Scalar;
			m_Y -= a_Scalar;
			m_Z -= a_Scalar;
			return *this;
		}
        
        inline vector3 operator+(const T a_Scalar) const{
            return vector3(
                m_X + a_Scalar,
                m_Y + a_Scalar,
                m_Z + a_Scalar
            );
        }

        inline vector3 operator-(const T a_Scalar) const{
            return vector3(
              m_X - a_Scalar,
              m_Y - a_Scalar,
              m_Z - a_Scalar
            );
        }

		inline vector3 operator*(const T a_Scalar) const{
            return vector3(
                m_X * a_Scalar,
                m_Y * a_Scalar,
                m_Z * a_Scalar
            );
        }
        
        inline vector3 operator/(const T a_Scalar) const{
            return vector3(
                m_X / a_Scalar,
                m_Y / a_Scalar,
                m_Z / a_Scalar
            );
        }

		inline friend vector3 operator*(T a_Scalar, vector3 a_Vector){
            return vector3(
              a_Vector.m_X * a_Scalar,
              a_Vector.m_Y * a_Scalar,
              a_Vector.m_Z * a_Scalar
            );
        }

		inline friend vector3 operator+(T a_Scalar, vector3 a_Vector){
            return vector3(
              a_Vector.m_X + a_Scalar,
              a_Vector.m_Y + a_Scalar,
              a_Vector.m_Z + a_Scalar
            );
        }

		inline friend vector3 operator-(T a_Scalar, vector3 a_Vector){
            return vector3(
              a_Scalar - a_Vector.m_X,
              a_Scalar - a_Vector.m_Y,
              a_Scalar - a_Vector.m_Z
            );
        }

		inline friend vector3 operator/(T a_Scalar, vector3 a_Vector){
            return vector3(
              a_Scalar / a_Vector.m_X,
              a_Scalar / a_Vector.m_Y,
              a_Scalar / a_Vector.m_Z
            );
        }

		inline vector3 operator*(vector3 a_Other) const{
            return vector3(
                m_X * a_Other.m_X,
                m_Y * a_Other.m_Y,
                m_Z * a_Other.m_Z
            );
        }

		inline vector3 operator/(vector3 a_Other) const{
            return vector3(
                m_X / a_Other.m_X,
                m_Y / a_Other.m_Y,
                m_Z / a_Other.m_Z
            );
        }
        
        inline vector3 operator+(const vector3 &a_Other) const{
			return vector3(
              m_X + a_Other.m_X, 
              m_Y + a_Other.m_Y, 
              m_Z + a_Other.m_Z
            );
		}
        
        inline vector3 operator-(const vector3 &a_Other) const{
			return vector3(
              m_X - a_Other.m_X, 
              m_Y - a_Other.m_Y, 
              m_Z - a_Other.m_Z
            );
		}
        
        inline vector3 operator-() const{
            return vector3(
                -m_X,
                -m_Y,
                -m_Z
            );
        }
	};

	struct color_tag {};
	struct vector_tag {};
	struct point_tag {};
	struct normal_tag {};
    
    typedef vector3<float, color_tag>  color3;
    typedef vector3<float, point_tag>  point3f;
	typedef vector3<float, vector_tag> vector3f;
	typedef vector3<float, normal_tag> normal3f;
    typedef vector3<int,   vector_tag> vector3i;

	template<typename _Type, typename _Tag>
	std::ostream& operator<<(std::ostream& a_Stream, const vector3<_Type, _Tag> &a_Vector){
		return a_Stream << '[' << a_Vector.m_X << ", " << a_Vector.m_Y << ", " << a_Vector.m_Z << ']';
	}
}
}
